#include <QGraphicsTextItem>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QPolygon>
#include <QImage>
#include <QPaintEvent>

#include <dynamicviewsettings.h>

#include "sequenceview.h"

///////// SequenceDiagramItem //////////////////////////////////////////////////////////////////////

SequenceDiagramItem::SequenceDiagramItem( ARSExecItem * startItem, ARSExecItem * endItem )
{
    m_startItem = startItem;
    m_endItem = endItem;
}

ARSExecItem * SequenceDiagramItem::startItem() const
{
    return m_startItem;
}

void SequenceDiagramItem::setStartItem( ARSExecItem * item )
{
    m_startItem = item;
}

ARSExecItem * SequenceDiagramItem::endItem() const
{
    return m_endItem;
}

void SequenceDiagramItem::setEndItem( ARSExecItem * item )
{
    m_endItem = item;
}

///////// SequenceDiagramModel /////////////////////////////////////////////////////////////////////

SequenceDiagramModel::SequenceDiagramModel( DynamicModel * model )
    : QAbstractTableModel(model)
{
    ARSExecItem * item = nextBlock(model->rootItem());

    while(item)
    {
        m_list.append(item);
        item = nextBlock(item);
    }

    ARSDummyObjectList tmpList = model->index()->objects(ARSObject::Form);
    foreach(ARSDummyObject * object, tmpList)
        m_formList.append(static_cast<ARSDummyForm *>(object));
}

int SequenceDiagramModel::rowCount( const QModelIndex & parent ) const
{
    Q_UNUSED(parent)
    return m_list.count();
}

int SequenceDiagramModel::columnCount( const QModelIndex & parent ) const
{
    Q_UNUSED(parent)
    return m_formList.count();
}

QVariant SequenceDiagramModel::data( const QModelIndex & index, int role ) const
{
    if(role == ItemRole)
    {
        ARSExecItem * item = m_list.at(index.row());
        ARSExecWorkflowBlock * block = static_cast<ARSExecWorkflowBlock *>(item->findParent(ARSExecItem::ItemTypeWorkflowBlock));
        if(!block)
            return QVariant();

        if(block->form() == m_formList.at(index.column()))
            return QVariant::fromValue(item);
    }

    if(role == HoverRole)
        return index == m_hovered;

    return QVariant();
}

QVariant SequenceDiagramModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
    if(orientation != Qt::Horizontal)
        return QVariant();

    if(role == Qt::DisplayRole)
    {
        return m_formList.at(section)->name();
    }

    return QVariant();
}

void SequenceDiagramModel::setHovered(const QModelIndex & index)
{
    if(index == m_hovered)
        return;

    m_hovered = index;
    emit dataChanged(index, index);
}

QModelIndex SequenceDiagramModel::hovered() const
{
    return m_hovered;
}

ARSExecItem * SequenceDiagramModel::nextBlock( ARSExecItem * item )
{
    ARSExecWorkflowBlock * block;
    ARSExecItem * nextBlockItem;

    if(item->type() == ARSExecItem::ItemTypeWorkflowBlock)
        block = static_cast<ARSExecWorkflowBlock *>(item);
    else
        block = static_cast<ARSExecWorkflowBlock *>(item->findParent(ARSExecItem::ItemTypeWorkflowBlock));

    nextBlockItem = item;

    do
    {
        nextBlockItem = nextBlockItem->nextItem();
    } while(nextBlockItem && ((!(nextBlockItem->type() & (ARSExecItem::ItemTypeWorkflowObject | ARSExecItem::ItemTypeAction))) ||
            nextBlockItem->findParent(ARSExecItem::ItemTypeWorkflowBlock) == block));

    return nextBlockItem;
}

SequenceDiagramDelegate::SequenceDiagramDelegate( QObject * parent )
    : QItemDelegate(parent)
{
    alImage = QImage(":/table/event_al");
    alImageHover = QImage(":/table/event_al_hover");
    filterImage = QImage(":/table/event_filter");
    filterImageHover = QImage(":/table/event_filter_hover");
}

void SequenceDiagramDelegate::paint( QPainter * painter, const QStyleOptionViewItem & option,
                                     const QModelIndex & index ) const
{
    ARSExecItem * item = index.data(Qt::UserRole).value<ARSExecItem *>();
    if(!item)
        return;

    ARSExecWorkflowBlock * block = static_cast<ARSExecWorkflowBlock *>(item->findParent(ARSExecItem::ItemTypeWorkflowBlock));
    if(!block)
        return;

    PropertyStyle * style = g_Settings.propertyStyle("ARSExecWorkflowBlock", "objectsType");
    PropertyAliasStyle * aliasStyle = style->styleForValue(block->objectsType());
    QColor aliasColor = aliasStyle->fgColor();
    QImage itemImage;
    if(block->objectsType() == ARSExecWorkflowObject::ActiveLink)
    {
        if(index.data(SequenceDiagramModel::HoverRole).toBool())
            itemImage = alImageHover;
        else
            itemImage = alImage;
    }
    else
    {
        if(index.data(SequenceDiagramModel::HoverRole).toBool())
            itemImage = filterImageHover;
        else
            itemImage = filterImage;
    }
//    if(index.data(SequenceDiagramModel::HoverRole).toBool())
//        aliasColor = aliasColor.lighter(200);

//    QImage adjustedImage = itemImage;
//    for(int i = 0; i < adjustedImage.width(); i++)
//        for(int j = 0; j < adjustedImage.height(); j++)
//        {
//            QColor color = adjustedImage.pixel(i, j);
//            color.setHsv(aliasColor.hue(), 255 - color.value(), color.value());
//            adjustedImage.setPixel(i, j, color.rgb());
//        }

    painter->drawImage(option.rect.center().x() - SequenceDiagramView::sectionWidth / 2, option.rect.top(),
                        itemImage);
//    painter->fillRect(option.rect.center().x() - SequenceDiagramView::sectionWidth / 2, option.rect.top(), SequenceDiagramView::sectionWidth,
//                      option.rect.height(), aliasColor);
}

///////// SequenceDiagramView  /////////////////////////////////////////////////////////////////////

SequenceDiagramView::SequenceDiagramView( QWidget * parent )
    : QTableView(parent)
{
    m_model = 0;

    setItemDelegate(new SequenceDiagramDelegate(this));
    verticalHeader()->setDefaultSectionSize(100);
    verticalHeader()->hide();
    setShowGrid(false);
    setVerticalScrollMode(ScrollPerPixel);
    setHorizontalScrollMode(ScrollPerPixel);
    setMouseTracking(true);
    horizontalHeader()->setSelectionMode(NoSelection);
}

void SequenceDiagramView::setDynamicModel( DynamicModel * model )
{
    m_model = new SequenceDiagramModel(model);
    setModel(m_model);
}

void SequenceDiagramView::paintEvent( QPaintEvent * event )
{
    QPainter painter(viewport());
    QHeaderView * hHeader = horizontalHeader();
    QHeaderView * vHeader = verticalHeader();
    ARSExecItem * prevBlock = 0;
    ARSExecItem * currentItem = 0;
    QColor actionColor;

    painter.setPen(QPen(QColor::fromHsv(0, 0, 200), 1, Qt::SolidLine));
    QFont font("MS Reference Sans Serif", 8);
    font.setStyleStrategy(QFont::PreferAntialias);
    painter.setFont(font);
    for(int i = 0; i < hHeader->count(); i++)
    {
        int sectionCenterX = hHeader->sectionPosition(i) + hHeader->sectionSize(i) / 2 - hHeader->offset();
        painter.drawLine(sectionCenterX, event->rect().top(), sectionCenterX, event->rect().bottom());
    }

    int prevItemX = -1;
    int itemX = -1;
    int prevCell = -1;
    int topIndex = vHeader->logicalIndexAt(0) - 1;
    if(topIndex  < 0)
        topIndex = 0;

    int bottomIndex = vHeader->logicalIndexAt(height());
    if(bottomIndex < 0 || bottomIndex >= vHeader->count())
        bottomIndex = vHeader->count() - 1;
    for(int vIndex = topIndex; vIndex <= bottomIndex; vIndex++)
    {
        QPoint itemPos;
        QVariant cellData;
        for(int i = 0; i < hHeader->count() && !cellData.isValid(); i++)
        {
            cellData = model()->data(model()->index(vIndex, i), Qt::UserRole);
            if(cellData.isValid())
            {
                currentItem = cellData.value<ARSExecItem *>();
                itemX = hHeader->sectionPosition(i) + hHeader->sectionSize(i) / 2 - hHeader->offset();

                if(prevCell != -1)
                {
                    bool isChild = prevBlock->isParentOf(currentItem);

                    PropertyAliasStyle * actionTypeStyle = 0;
                    if(isChild)
                    {
                        ARSExecAction * action = static_cast<ARSExecAction *>(currentItem->findParent(ARSExecItem::ItemTypeAction));
                        if(action)
                        {
                            PropertyStyle * actionStyle = g_Settings.propertyStyle("ARSExecAction", "actionType");
                            actionTypeStyle = actionStyle->styleForValue(action->actionType());
                        }
                    }

                    if(actionTypeStyle)
                        actionColor = actionTypeStyle->fgColor();
                    else
                        actionColor = QColor(100, 100, 100);

                    if(isChild)
                        painter.setPen(QPen(actionColor, 2, Qt::SolidLine));
                    else
                        painter.setPen(QPen(actionColor, 2, Qt::DotLine));

                    if(prevCell == i)
                    {
                        ARSExecItem * currentItemBlock = currentItem->findParent(ARSExecItem::ItemTypeWorkflowBlock);
                        if(isChild || (currentItemBlock && currentItemBlock->isParentOf(prevBlock)))
                        {
                            itemPos.setX(itemX + sectionWidth / 2);
                            itemPos.setY(vHeader->sectionPosition(vIndex) - vHeader->offset() + 20);
                            painter.setRenderHint(QPainter::Antialiasing, true);
                            painter.drawArc(itemPos.x() - 20, itemPos.y() - 40, 40, 40, 90 * 16, -155 * 16);

                            QPolygon arrow;
                            arrow.append(itemPos + QPoint(0, -2));
                            arrow.append(itemPos + QPoint(8, 2));
                            arrow.append(itemPos + QPoint(8, -6));
                            arrow.append(itemPos + QPoint(0, -2));

                            QPainterPath path;
                            path.addPolygon(arrow);

                            painter.setPen(QPen(actionColor, 2, Qt::SolidLine));

                            if(isChild)
                                painter.fillPath(path, actionColor);
                            else
                                painter.drawPath(path);

                            painter.setRenderHint(QPainter::Antialiasing, false);

                            if(isChild)
                            {
                                painter.drawText(itemX + sectionWidth / 2 + 30,
                                                 vHeader->sectionPosition(vIndex) - vHeader->offset(),
                                                 actionTypeStyle->alias());
                            }
                        }
                    }
                    else
                    {
                        QPoint prevItemPos;

                        if(itemX > prevItemX)
                        {
                            itemPos.setX(itemX - sectionWidth / 2 - 8);
                            prevItemPos.setX(prevItemX + sectionWidth / 2);
                        }
                        else
                        {
                            itemPos.setX(itemX + sectionWidth / 2 + 8);
                            prevItemPos.setX(prevItemX - sectionWidth / 2);
                        }

                        itemPos.setY(vHeader->sectionPosition(vIndex) - vHeader->offset());
                        prevItemPos.setY(itemPos.y());

                        painter.drawLine(prevItemPos, itemPos);

                        QPolygon arrow;
                        arrow.append(itemPos + QPoint(0, 4));
                        arrow.append(itemPos + QPoint(0, -4));

                        if(itemPos.x() > prevItemPos.x())
                            arrow.append(itemPos + QPoint(8, 0));
                        else
                            arrow.append(itemPos - QPoint(8, 0));

                        arrow.append(itemPos + QPoint(0, 4));

                        QPainterPath path;
                        path.addPolygon(arrow);
                        painter.setPen(QPen(actionColor, 2, Qt::SolidLine));
                        painter.setRenderHint(QPainter::Antialiasing, true);
                        if(isChild)
                            painter.fillPath(path, actionColor);
                        else
                            painter.drawPath(path);

                        painter.setRenderHint(QPainter::Antialiasing, false);

                        if(isChild)
                        {
                            QRect actionRect;

                            if(itemPos.x() > prevItemPos.x())
                                actionRect = QRect(QPoint(prevItemPos.x(), prevItemPos.y() - 16), QPoint(itemPos.x(), itemPos.y()));
                            else
                                actionRect = QRect(QPoint(itemPos.x(), itemPos.y() - 16), QPoint(prevItemPos.x(), prevItemPos.y()));

                            painter.drawText(actionRect, Qt::AlignCenter, actionTypeStyle->alias());
                        }
                    }
                }

                prevItemX = itemX;
                prevBlock = currentItem->findParent(ARSExecItem::ItemTypeWorkflowBlock);
                prevCell = i;

                TextStyle * headerStyle = g_Settings.itemStyle("ARSExecItem")->exclusiveStyle(0);
                QFontMetrics fontMetrics(painter.font());
                if(prevBlock)
                {
                    ARSExecWorkflowBlock * block = static_cast<ARSExecWorkflowBlock *>(prevBlock);
                    PropertyStyle * blockStyle = g_Settings.propertyStyle("ARSExecWorkflowBlock", "event");
                    PropertyAliasStyle * blockAliasStyle = blockStyle->styleForValue(block->event());
                    if(blockAliasStyle)
                    {
                        painter.setPen(headerStyle->fgColor());
                        painter.drawText(itemX + sectionWidth / 2 + 5,
                                         vHeader->sectionPosition(vIndex) - vHeader->offset() + 35,
                                         blockStyle->label() + blockStyle->separator());

                        painter.setPen(blockAliasStyle->fgColor());
                        painter.drawText(itemX + sectionWidth / 2 + 5 + fontMetrics.width(blockStyle->label() + blockStyle->separator()) + 4,
                                         vHeader->sectionPosition(vIndex) - vHeader->offset() + 35,
                                         blockAliasStyle->alias());
                    }

                    if(block->objectsType() == ARSExecWorkflowObject::Filter)
                    {
                        ARSExecFltrBlock * fltrBlock = static_cast<ARSExecFltrBlock *>(block);
                        blockStyle = g_Settings.propertyStyle("ARSExecFltrBlock", "phase");
                        blockAliasStyle = blockStyle->styleForValue(fltrBlock->phase());
                        if(blockAliasStyle)
                        {
                            painter.setPen(headerStyle->fgColor());
                            painter.drawText(itemX + sectionWidth / 2 + 5,
                                             vHeader->sectionPosition(vIndex) - vHeader->offset() + 50,
                                             blockStyle->label() + blockStyle->separator());

                            painter.setPen(blockAliasStyle->fgColor());
                            painter.drawText(itemX + sectionWidth / 2 + 5 + fontMetrics.width(blockStyle->label() + blockStyle->separator()) + 4,
                                             vHeader->sectionPosition(vIndex) - vHeader->offset() + 50,
                                             blockAliasStyle->alias());
                        }

                        if(!fltrBlock->requestID().isEmpty())
                        {
                            blockStyle = g_Settings.propertyStyle("ARSExecFltrBlock", "requestID");
                            painter.setPen(headerStyle->fgColor());
                            painter.drawText(itemX + sectionWidth / 2 + 5,
                                             vHeader->sectionPosition(vIndex) - vHeader->offset() + 65,
                                             blockStyle->label() + blockStyle->separator());

                            painter.setPen(blockStyle->fgColor());
                            painter.drawText(itemX + sectionWidth / 2 + 5 + fontMetrics.width(blockStyle->label() + blockStyle->separator()) + 4,
                                             vHeader->sectionPosition(vIndex) - vHeader->offset() + 65,
                                             fltrBlock->requestID());
                        }

                    }
                    else
                    {
                        ARSExecActlBlock * actlBlock = static_cast<ARSExecActlBlock *>(block);
                        blockStyle = g_Settings.propertyStyle("ARSExecActlBlock", "screenType");
                        blockAliasStyle = blockStyle->styleForValue(actlBlock->screenType());
                        if(blockAliasStyle)
                        {
                            painter.setPen(headerStyle->fgColor());
                            painter.drawText(itemX + sectionWidth / 2 + 5,
                                             vHeader->sectionPosition(vIndex) - vHeader->offset() + 50,
                                             blockStyle->label() + blockStyle->separator());

                            painter.setPen(blockAliasStyle->fgColor());
                            painter.drawText(itemX + sectionWidth / 2 + 5 + fontMetrics.width(blockStyle->label() + blockStyle->separator()) + 4,
                                             vHeader->sectionPosition(vIndex) - vHeader->offset() + 50,
                                             blockAliasStyle->alias());
                        }

                        if(actlBlock->linkedField())
                        {
                            blockStyle = g_Settings.propertyStyle("ARSExecActlBlock", "linkedField");
                            painter.setPen(headerStyle->fgColor());
                            painter.drawText(itemX + sectionWidth / 2 + 5,
                                             vHeader->sectionPosition(vIndex) - vHeader->offset() + 65,
                                             blockStyle->label() + blockStyle->separator());

                            painter.setPen(blockStyle->fgColor());
                            painter.drawText(itemX + sectionWidth / 2 + 5 + fontMetrics.width(blockStyle->label() + blockStyle->separator()) + 4,
                                             vHeader->sectionPosition(vIndex) - vHeader->offset() + 65,
                                             actlBlock->linkedField()->name());
                        }
                    }
                }
            }
        }
    }

    QTableView::paintEvent(event);
}

void SequenceDiagramView::mouseMoveEvent( QMouseEvent * event )
{
    QModelIndex index = indexAt(event->pos());
    if(!index.isValid())
        return;

    if(index.data(Qt::UserRole).isValid())
    {
        QHeaderView * hHeader = horizontalHeader();
        int itemX = hHeader->sectionPosition(index.column()) + hHeader->sectionSize(index.column()) / 2 - hHeader->offset();
        if(event->pos().x() > itemX - sectionWidth / 2 && event->pos().x() < itemX + sectionWidth / 2)
        {
            setCursor(Qt::PointingHandCursor);
            m_model->setHovered(index);
//            repaint();
        }
        else
        {
            setCursor(Qt::ArrowCursor);
            m_model->setHovered(QModelIndex());
        }
    }
    else
    {
        setCursor(Qt::ArrowCursor);
        m_model->setHovered(QModelIndex());
    }
}

void SequenceDiagramView::mousePressEvent( QMouseEvent *event )
{
    QModelIndex index = indexAt(event->pos());
    if(!index.isValid())
        return;

    if(index.data(Qt::UserRole).isValid())
    {
        ARSExecItem * item = index.data(Qt::UserRole).value<ARSExecItem *>();
        QHeaderView * hHeader = horizontalHeader();
        int itemX = hHeader->sectionPosition(index.column()) + hHeader->sectionSize(index.column()) / 2 - hHeader->offset();
        if(event->pos().x() > itemX - sectionWidth / 2 && event->pos().x() < itemX + sectionWidth / 2)
            emit openItem(item);
    }
}

///////// SequenceViewWidget ///////////////////////////////////////////////////////////////////////

SequenceViewWidget::SequenceViewWidget( QWidget * parent )
    : DynamicViewWidget(parent)
{
    QFile styleFile(":/qss/tableview");
    styleFile.open(QIODevice::ReadOnly);
    setStyleSheet(styleFile.readAll());
    styleFile.close();

    QHBoxLayout * mainLayout = new QHBoxLayout(this);
    mainLayout->setContentsMargins(0, 0, 0, 0);

    m_view = new SequenceDiagramView(this);
    connect(m_view, SIGNAL(openItem(ARSExecItem*)), this, SIGNAL(openItem(ARSExecItem*)));
    mainLayout->addWidget(m_view);

    setLayout(mainLayout);
}

QString SequenceViewWidget::pluginName() const
{
    return "sequenceview";
}

void SequenceViewWidget::connectToolBarItems(const ToolBarItemList & items)
{
}

void SequenceViewWidget::disconnectToolBarItems(const ToolBarItemList & items)
{
}

void SequenceViewWidget::adjustToolbar(const ToolBarItemList & itemsList) const
{
}

void SequenceViewWidget::setModel(DynamicModel * model)
{
    m_view->setDynamicModel(model);
}

///////// SequenceViewPlugin ///////////////////////////////////////////////////////////////////////

SequenceViewPlugin::SequenceViewPlugin(QObject * parent)
    : QObject(parent)
{
}

QString SequenceViewPlugin::name()const
{
    return "sequenceview";
}

QString SequenceViewPlugin::description()const
{
    return tr("This plugin provides sequence diagram representation of ARS log files");
}

bool SequenceViewPlugin::loadSettings()
{
    return true;
}

QString SequenceViewPlugin::viewName()const
{
    return tr("Sequece");
}

ViewWidget::ViewType SequenceViewPlugin::viewType() const
{
    return ViewWidget::DynamicModelView;
}

QIcon SequenceViewPlugin::icon(bool active)const
{
    return QIcon(":/view/icon");
}
ViewWidget * SequenceViewPlugin::createView(QWidget * parent)const
{
    return new SequenceViewWidget(parent);
}

ToolBarItemList SequenceViewPlugin::toolBarItems(QObject * parent)const
{
    return ToolBarItemList();
}

SettingsWidget * SequenceViewPlugin::settingsWidget(QWidget * parent)const
{
    return 0;
}

void SequenceViewPlugin::appEvent(AppEvent * event)
{
}


////////////////////////////////////////////////////////////////////////////////////////////////////

Q_EXPORT_PLUGIN2(sequenceview, SequenceViewPlugin)

////////////////////////////////////////////////////////////////////////////////////////////////////
